using System;
using System.Collections;
using System.Text;

// string encoding complete

namespace csammisrun.OscarLib.Utility
{
	/// <summary>
	/// Provides static methods for processing messages in SNAC family 0x0001 -- generic service controls
	/// </summary>
	internal static class SNAC01
	{
		private const int SERVICE_RESPONSE_VERSION = 0x0001;
		private const int SERVICE_RESPONSE_FAMILY = 0x000D;
		private const int SERVICE_RESPONSE_ADDRESS = 0x0005;
		private const int SERVICE_RESPONSE_COOKIE = 0x0006;

		private const int MOTD_MESSAGE = 0x000B;
		private const int MOTD_UNKNOWN_1 = 0x0002;
		private const int MOTD_UNKNOWN_2 = 0x0003;

		private const int EXTSTATUS_ICON1 = 0x0000;
		private const int EXTSTATUS_ICON2 = 0x0001;
		private const int EXTSTATUS_AVAILMESSAGE = 0x0002;

		private const int ICONFLAGS_REQUESTICON = 0x81;
		private const int ICONFLAGS_UPLOADICON = 0x41;
    private const int ICONFLAGS_UPLOADSUCCESS = 0x01;

		/// <summary>
		/// Notifies the BOS server that the client is ready to go online -- SNAC(01,02)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object</param>
		/// <remarks>
		/// The client sends this SNAC to notify the server that it is ready to receive messages
		/// and online notifications.
		/// <para>
		/// This SNAC must be sent within 30 seconds of connection, or the connection
		/// will drop. This should not ever be a problem, barring a lost connection well below
		/// the level of this library.
		/// </para>
		/// </remarks>
		public static void SendReadyNotification(DataPacket dp)
		{
			// Build SNAC(01,02)
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.GenericServiceControls;
			sh.FamilySubtypeID = (ushort)GenericServiceControls.ClientOnline;
			sh.Flags = 0x0000;
			sh.RequestID = dp.ParentSession.GetNextRequestID();

			ConnectionManager cm = dp.ParentSession.Connections;
			ushort[] families = cm.GetFamilies(dp.ParentConnection);
			FamilyManager fm = dp.ParentSession.Families;

			Array.Sort(families);
			Array.Reverse(families);

      bool ischatroom = false;
			byte[] buffer = new byte[families.Length * 4 * 2];
			DataPacket[][] delayedframes = new DataPacket[families.Length][];

			int index = 0, i = 0;
			foreach(ushort f in families)
			{
				Marshal.InsertUshort(buffer, f, ref index);
				Marshal.InsertUshort(buffer, fm.GetFamilyVersion(f), ref index);
				Marshal.InsertUshort(buffer, fm.GetFamilyToolID(f), ref index);
				Marshal.InsertUshort(buffer, fm.GetFamilyToolVersion(f), ref index);
				delayedframes[i++] = dp.ParentSession.Connections.GetDelayedPackets(f);

        if (f == 0x000E)
          ischatroom = true;
			}

      /*
       * The initial service connection has to send SNAC(01,1E) before it actually
       * sends SNAC(01,02), thus the check for the initial connection here
       * and after it gets sent.
       */
      Session sess = dp.ParentSession;
      if (!sess.LoggedIn)
      {
        SNAC01.SetExtendedStatus(sess, sess.AvailableMessage, ICQFlags.Normal, ICQStatus.Online);
        SNAC01.SetExtendedStatus(sess, null, ICQFlags.Normal, ICQStatus.Online);
      }

      // The connection is done, so start sending keepalives
      dp.ParentConnection.Connecting = false;
      dp.ParentConnection.ReadyForData = true;

      // Build and send a new DataPacket instead of re-using the originator;
      // SNAC(01,02) was getting sent twice on child connections with the reuse
      DataPacket dp2 = Marshal.BuildDataPacket(dp.ParentSession, sh, buffer);
      dp2.ParentConnection = dp.ParentConnection;
      SNACFunctions.BuildFLAP(dp2);

			/*
			 * If this is the initial service connection, there are a few things
			 * remaining to do before signaling the client app that we're done.
			 * Otherwise, this is a new service connection, and there is probably at least one
			 * delayed packet. Process those packets. Additionally, if the new connection is
       * a chatroom connection, query the Rendezvous manager to see if it is the result
       * of an explict room creation (or implict, as in accepting a chat invitation).
       * If it was explict, notify the user that it's done
			 */
			if(!sess.LoggedIn)
			{
				SNAC02.SetSelfLocation(sess);
				SNAC01.SetIdleTime(sess, 0);
			}
			else
			{
				foreach(DataPacket[] list in delayedframes)
				{
					if(list == null) continue;
					foreach(DataPacket dp_delay in list)
					{
						dp_delay.ParentConnection = dp.ParentConnection;
						SNACFunctions.DispatchToRateClass(dp_delay);
					}
				}

        if (ischatroom)
        {
          // Check with the rendezvous manager to see if this was
          // an explict creation or just accepting an invite
          ChatRoomInfo cri = sess.Connections.GetChatByConnection((ChatConnection)dp.ParentConnection);
          if (ChatInvitationConnection.IsExplictChatCreation(cri.DisplayName, cri.Exchange))
          {
            sess.OnChatRoomCreated(cri);
          }
        }
			}
		}

		/// <summary>
		/// Processes the server-supported family list -- SNAC(01,03)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object containing SNAC(01,03)</param>
		public static void ProcessServicesList(DataPacket dp)
		{
			Session sess = dp.ParentSession;
			byte[] SNACData = dp.Data;

			int index = 0;
			// Get the list of supported families and populate the session list
			while(index < SNACData.Length)
			{
				ushort family = Marshal.ByteArrayToUshort(SNACData, ref index);
				// Add family to connection list, unless it is 0x0001, because all
				// connections (except authorization) handle this family
				if(family == (ushort)SNACFamily.GenericServiceControls)
					continue;
				else
					sess.Connections.AssignFamily(family, dp.ParentConnection);
			}

			// Request the server versions of these services
			SNAC01.RequestServiceVersions(dp);
		}

		/// <summary>
		/// Sends a request for a new service family -- SNAC(01,04)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		/// <param name="newfamily">The new family to request</param>
		public static void RequestNewService(Session sess, ushort newfamily)
		{
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.GenericServiceControls;
			sh.FamilySubtypeID = (ushort)GenericServiceControls.NewServiceRequest;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			int index = 0;
			byte[] data = new byte[2];
			Marshal.InsertUshort(data, newfamily, ref index);

			// This SNAC expects a response in SNAC(01,05)
			sess.StoreRequestID(sh.RequestID, null);

			DataPacket dp = Marshal.BuildDataPacket(sess, sh, data);
			// This one is always sent on BOS
			dp.ParentConnection = sess.Connections.BOSConnection;
			SNACFunctions.BuildFLAP(dp);
		}

    /// <summary>
    /// Sends a request for a new chat room connection -- SNAC(01,04)
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    /// <param name="roominfo">A <see cref="ChatRoomInfo"/> object</param>
    public static void RequestChatService(Session sess, ChatRoomInfo roominfo)
    {
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.GenericServiceControls;
      sh.FamilySubtypeID = (ushort)GenericServiceControls.NewServiceRequest;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      Encoding enc = Marshal.ASCII;
      byte roomnamelength = (byte)enc.GetByteCount(roominfo.FullName);

      int index = 0;
      int tlvlength = 2 + 1 + roomnamelength + 2;
      byte[] data = new byte[2 + 4 + tlvlength];
      Marshal.InsertUshort(data, 0x000E, ref index);
      // Chat room info goes into TLV 0x0001
      Marshal.InsertUshort(data, 0x0001, ref index);
      Marshal.InsertUshort(data, (ushort)tlvlength, ref index);

      Marshal.InsertUshort(data, roominfo.Exchange, ref index);
      data[index++] = roomnamelength;
      Marshal.InsertString(data, roominfo.FullName, enc, ref index);
      Marshal.InsertUshort(data, roominfo.Instance, ref index);

      sess.StoreRequestID(sh.RequestID, roominfo);

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, data);
      // This one is always sent on BOS
      dp.ParentConnection = sess.Connections.BOSConnection;
      SNACFunctions.BuildFLAP(dp);
    }

		/// <summary>
		/// Processes the server response to a new family request -- SNAC(01,05)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object containing SNAC(01,05)</param>
		public static void ProcessNewServiceResponse(DataPacket dp)
		{
			byte[] SNACData = dp.Data;

			int index = 0;
			ushort family = 0, version = 0xFFFF;
			string BOSaddress = "";
			byte[] cookie = null;
			ushort key, keylength;

      if (SNACData[0] == 0x00 && SNACData[1] == 0x06)
      {
        index += 2;	// What the heck is this...0x0006, some families, some of the time?
      }

			while(index < SNACData.Length)
			{
				key = Marshal.ByteArrayToUshort(SNACData, ref index);
				keylength = Marshal.ByteArrayToUshort(SNACData, ref index);
				switch(key)
				{
					case SNAC01.SERVICE_RESPONSE_VERSION:
						version = Marshal.ByteArrayToUshort(SNACData, ref index);
						break;
					case SNAC01.SERVICE_RESPONSE_FAMILY:
						family = Marshal.ByteArrayToUshort(SNACData, ref index);
						break;
					case SNAC01.SERVICE_RESPONSE_ADDRESS:
						BOSaddress = Marshal.ByteArrayToString(SNACData, ref index, keylength, Marshal.ASCII);
						break;
					case SNAC01.SERVICE_RESPONSE_COOKIE:
						cookie = new byte[keylength];
						Marshal.CopyArray(SNACData, cookie, ref index);
						break;
					default:
						index += keylength;
						break;
				}
			}

      Connection newconn = null;
      object store = dp.ParentSession.RetrieveRequestID(dp.SNAC.RequestID);

      if (family != 0x000E)
      {
        newconn = dp.ParentSession.Connections.CreateNewConnection(family);
      }
      else
      {
        ChatRoomInfo roominfo = (ChatRoomInfo)store;
        dp.ParentSession.Connections.CreateNewChatConnection(roominfo);
        newconn = roominfo.Connection;
      }

			string[] bosinfo = BOSaddress.Split(':');
			int port = 0;
			if(bosinfo.Length == 2)
			{
				port = Int32.Parse(bosinfo[1]);
			}
			else
			{
				port = dp.ParentSession.LoginPort;
			}

      newconn.ServerConnnectionCompleted += new ServerConnectionCompletedHandler(newconn_ServerConnnectionCompleted);
      newconn.Server = bosinfo[0];
      newconn.Port = port;
      newconn.Cookie = cookie;
			newconn.ConnectToServer();

			// The connection process continues when the server sends SNAC(01,03)
		}

    static void newconn_ServerConnnectionCompleted(Connection conn)
    {
      conn.ReadHeader();
    }

		/// <summary>
		/// Sends a request for rate information -- SNAC(01,06)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object</param>
		public static void RequestRateInformation(DataPacket dp)
		{
			Session sess = dp.ParentSession;

			// Construct SNAC (01,06)
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (byte)SNACFamily.GenericServiceControls;
			sh.FamilySubtypeID = (byte)GenericServiceControls.RateLimitRequest;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			// No SNAC data
			dp.SNAC = sh;
			dp.Data = null;
			SNACFunctions.BuildFLAP(dp);
		}

		/// <summary>
		/// Processes the list of rate limitations -- SNAC(01,07)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object containing SNAC(01,07)</param>
		public static void ProcessRateLimitations(DataPacket dp)
		{
			Session sess = dp.ParentSession;
			byte[] SNACData = dp.Data;

			int index = 0;
			int num_classes = Marshal.ByteArrayToUshort(SNACData, ref index);
			Hashtable rcs = new Hashtable();
      ushort[] classes = new ushort[num_classes];

      RateClassManager rcm = sess.RateClasses;

			// Get the rate class attributes out of the SNAC
			for(int i = 0; i < num_classes; i++)
			{
				ushort id = Marshal.ByteArrayToUshort(SNACData, ref index);
				RateClass rc = rcm.GetByID(id);
				rc.WindowSize = Marshal.ByteArrayToUint(SNACData, ref index);
				rc.ClearLevel = Marshal.ByteArrayToUint(SNACData, ref index);
				rc.AlertLevel = Marshal.ByteArrayToUint(SNACData, ref index);
				rc.LimitLevel = Marshal.ByteArrayToUint(SNACData, ref index);
				rc.DisconnectLevel = Marshal.ByteArrayToUint(SNACData, ref index);
				rc.CurrentLevel = Marshal.ByteArrayToUint(SNACData, ref index);
				rc.MaxLevel = Marshal.ByteArrayToUint(SNACData, ref index);
				if(dp.ParentSession.Families.GetFamilyVersion(0x0001) >= 3)
				{
					rc.LastTime = Marshal.ByteArrayToUint(SNACData, ref index);
					rc.CurrentState = SNACData[index++];
				}
				rcm.SetByID(id, rc);
        rc.StartLimitedTransmission();

				classes[i] = id;
			}

			// Register rates with the session's ConnectionList
			for(int i=0; i < num_classes; i++)
			{
				ushort id = Marshal.ByteArrayToUshort(SNACData, ref index);
				ushort num_pairs = Marshal.ByteArrayToUshort(SNACData, ref index);

				for(int j=0; j < num_pairs; j++)
				{
					ushort family = Marshal.ByteArrayToUshort(SNACData, ref index);
					ushort subtype = Marshal.ByteArrayToUshort(SNACData, ref index);
					rcm.SetRateClassKey(family, subtype, id);
				}
        
			}

			SNAC01.AcknowledgeRateLimitations(dp, classes);
		}

		/// <summary>
		/// Sends an acknowledgement of the server's rate limitations -- SNAC(01,08)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object</param>
		/// <param name="classes">The known rate classes to acknowledge</param>
		public static void AcknowledgeRateLimitations(DataPacket dp, ushort[] classes)
		{
			Session sess = dp.ParentSession;
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.GenericServiceControls;
			sh.FamilySubtypeID = (ushort)GenericServiceControls.AcknowledgeRateLimits;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			int index = 0;
			byte[] buffer = new byte[classes.Length * 2];
			for(int i = 0; i < classes.Length; i++)
			{
				index = i * 2;
				Marshal.InsertUshort(buffer, classes[i], ref index);
			}

			dp.SNAC = sh;
			dp.Data = buffer;
			SNACFunctions.BuildFLAP(dp);

			sess.OnLoginStatusUpdate("Protocol negotiation complete", 0.66);

			/*
			 * If this is the initial services connection, we call the remaining SNACs
			 * in the login sequence.
			 * Otherwise, this is the last step in setting up a new service connection,
			 * and we send SNAC(01,02) here.
			 */
			if(!sess.LoggedIn)
			{
				// Start stage 3, services setup
				SNAC01.RequestOwnInformation(sess);
				SNAC13.RequestParametersList(sess);
				SNAC13.RequestInitialContactList(sess);
				SNAC02.RequestParametersList(sess);
				SNAC03.RequestParametersList(sess);
				SNAC04.RequestParametersList(sess);
				SNAC09.RequestParametersList(sess);
				SNAC02.ReportCapabilitiesList(sess);
				SNAC02.SetProfileAwayMessage(sess, sess.Profile, sess.ProfileEncoding, null, null);
			}
			else
			{
				SNAC01.SendReadyNotification(dp);
			}
		}

		/// <summary>
		/// Processes a rate deletion message from the server -- SNAC(01,09)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(01,09)</param>
		public static void ProcessRateDeletion(DataPacket dp)
		{
			byte[] SNACData = dp.Data;

			int index = 0;
			ushort[] rategroups = new ushort[SNACData.Length / 2];
			for(int i = 0; index < SNACData.Length; i++)
			{
				rategroups[i] = Marshal.ByteArrayToUshort(SNACData, ref index);
			}

			// Do something with these rate groups
		}

    /// <summary>
    /// Process a rate change message from the server -- SNAC(01,0A)
    /// </summary>
    /// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(01,0A)</param>
    public static void ProcessRateChange(DataPacket dp)
    {
      byte[] SNACData = dp.Data;
    }

		/// <summary>
		/// Processes a pause request from the server -- SNAC(01,0B)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(01,0B)</param>
		public static void ProcessPauseRequest(DataPacket dp)
		{
			byte[] SNACData = dp.Data;

			// I'm not going to support a bifurcated split, because it really screws with the
			// model I've got right now.

			SNAC01.SendPauseAcknowledgement(dp);
		}

		/// <summary>
		/// Sends an acknowledgement to the pause command -- SNAC(01,0C)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object to be paused</param>
		public static void SendPauseAcknowledgement(DataPacket dp)
		{
			Session sess = dp.ParentSession;

			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.GenericServiceControls;
			sh.FamilySubtypeID = (ushort)GenericServiceControls.PauseResponse;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			ushort[] families = sess.Connections.GetFamilies(dp.ParentConnection);
			byte[] data = new byte[families.Length * 2];
			for(int i = 0, j = 0; i < families.Length; i++)
			{
				Marshal.InsertUshort(data, families[i], ref j);
			}

			dp.SNAC = sh;
			dp.Data = data;
			SNACFunctions.BuildFLAP(dp);
		}

    /// <summary>
    /// Processes a resume request from the server -- SNAC(01,0D)
    /// </summary>
    /// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(01,0D)</param>
    public static void ProcessResumeRequest(DataPacket dp)
    {
      // Tell the connection it can start sending again
    }

		/// <summary>
		/// Requests the client's own online information -- SNAC(01,0E)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		public static void RequestOwnInformation(Session sess)
		{
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.GenericServiceControls;
			sh.FamilySubtypeID = (ushort)GenericServiceControls.OwnInformationRequest;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			// This SNAC expects a response in SNAC(01,0F)
			sess.StoreRequestID(sh.RequestID, null);

			DataPacket dp = Marshal.BuildDataPacket(sess, sh, null);
			// Always gets sent on BOS
			dp.ParentConnection = sess.Connections.BOSConnection;
			SNACFunctions.BuildFLAP(dp);
		}

		/// <summary>
		/// Processes the client's own online information -- SNAC(01,0F)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(01,0F)</param>
		public static void ProcessOwnInformation(DataPacket dp)
		{
      int index = 0;
      bool valid = false;
      UserInfo ui = Marshal.GetUserinfoBlock(dp.Data, ref index, ref valid);
			dp.ParentSession.RetrieveRequestID(dp.SNAC.RequestID);
		}

		/// <summary>
		/// Processes a warning notification from the server -- SNAC(01,10)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(01,10)</param>
		public static void ProcessWarningMessage(DataPacket dp)
		{
			byte[] SNACData = dp.Data;

			int index = 0;
			bool anonymous = false;
			UserInfo ui;

			ushort newwarning = Marshal.ByteArrayToUshort(SNACData, ref index);
			if(index < SNACData.Length)
			{
				ui = Marshal.GetUserinfoBlock(SNACData, ref index);
				anonymous = false;
			}
			else
			{
				ui = new UserInfo();
				anonymous = true;
			}

			// Alert the client that it has been warned
			dp.ParentSession.OnWarningReceived(newwarning, anonymous, ui);
		}

		/// <summary>
		/// Sets the client's idle time -- SNAC(01,11)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		/// <param name="seconds">The client's idle time, in seconds</param>
		/// <remarks>
		/// <para>
		/// To remove idle status, set <paramref name="seconds"/> to zero.
		/// </para>
		/// <para>
		/// This SNAC should be sent only once to set the client as idle,
		/// or to remove idle status. The server updates the client's idle time
		/// automatically, there is no need to send multiple idle time messages.
		/// </para>
		/// </remarks>
		public static void SetIdleTime(Session sess, uint seconds)
		{
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.GenericServiceControls;
			sh.FamilySubtypeID = (ushort)GenericServiceControls.SetIdleTime;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			int index = 0;
			byte[] buffer = new byte[4];
			Marshal.InsertUint(buffer, seconds, ref index);

			DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
			// Always gets sent on BOS
			dp.ParentConnection = sess.Connections.BOSConnection;
			SNACFunctions.BuildFLAP(dp);
		}

		/// <summary>
		/// Processes a server migration notification -- SNAC(01,12)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(01,12)</param>
		public static void ProcessMigrationNotice(DataPacket dp)
		{
			byte[] SNACData = dp.Data;

			int index = 0;
			ushort family_count = Marshal.ByteArrayToUshort(SNACData, ref index);
			ushort[] families = null;
			if(family_count > 0)
			{
				families = new ushort[family_count];
				for(int i = 0; i < family_count; i++)
					families[i] = Marshal.ByteArrayToUshort(SNACData, ref index);
			}

			ushort key, keylength;
			string BOSaddress;
			byte[] cookie;

			while(index < SNACData.Length)
			{
				key = Marshal.ByteArrayToUshort(SNACData, ref index);
				keylength = Marshal.ByteArrayToUshort(SNACData, ref index);
				switch(key)
				{
					case SNAC01.SERVICE_RESPONSE_ADDRESS:
						BOSaddress = Marshal.ByteArrayToString(SNACData, ref index, keylength, Marshal.ASCII);
						break;
					case SNAC01.SERVICE_RESPONSE_COOKIE:
						cookie = new byte[keylength];
						Marshal.CopyArray(SNACData, cookie, ref index);
						break;
					default:
						index += keylength;
						break;
				}
			}

			// Process migration
			dp.ParentSession.OnError(ServerErrorCode.OscarLibUnsupportedFunction);
		}

		/// <summary>
		/// Processes the Message Of The Day -- SNAC(01,13)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(01,13)</param>
		public static void ProcessMOTD(DataPacket dp)
		{
			byte[] SNACData = dp.Data;

			int index = 0;
			ushort motdtype = Marshal.ByteArrayToUshort(SNACData, ref index);
			string messagestring = "";
			ushort data = 0;
			while(index < SNACData.Length)
			{
				ushort key = Marshal.ByteArrayToUshort(SNACData, ref index);
				ushort keylength = Marshal.ByteArrayToUshort(SNACData, ref index);
				switch(key)
				{
					case SNAC01.MOTD_MESSAGE:
						messagestring = Marshal.ByteArrayToString(SNACData, ref index, keylength, Marshal.ASCII);
						break;
					case SNAC01.MOTD_UNKNOWN_1:
						data = Marshal.ByteArrayToUshort(SNACData, ref index);
						break;
					case SNAC01.MOTD_UNKNOWN_2:
						data = Marshal.ByteArrayToUshort(SNACData, ref index);
						break;
					default:
						index += keylength;
						break;
				}
			}

			// Signal the client that the MOTD has come through
			dp.ParentSession.OnMessageOfTheDayReceived(motdtype, messagestring);
		}

		/// <summary>
		/// Sets the client's privacy settings -- SNAC(01,14)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		/// <param name="settings">The privacy flags to send to the server</param>
		public static void SetPrivacySettings(Session sess, uint settings)
		{
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.GenericServiceControls;
			sh.FamilySubtypeID = (ushort)GenericServiceControls.SetPrivacyFlags;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			DataPacket dp = Marshal.BuildDataPacket(sess, sh, Marshal.UIntToByteArray(settings));
			// Always gets sent on BOS
			dp.ParentConnection = sess.Connections.BOSConnection;
			SNACFunctions.BuildFLAP(dp);
		}

		/// <summary>
		/// (Obsolete) Sends a keepalive no-op -- SNAC(01,16)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		/// <remarks>
		/// This SNAC is not used anymore. Channel 5 FLAPs should be used
		/// for keepalive, and are sent automatically by the <see cref="Connection"/>
		/// objects.
		/// </remarks>
		public static void SendNoOp(Session sess)
		{
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.GenericServiceControls;
			sh.FamilySubtypeID = (ushort)GenericServiceControls.NoOp;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			DataPacket dp = Marshal.BuildDataPacket(sess, sh, null);
			SNACFunctions.BuildFLAP(dp);
		}

		/// <summary>
		/// Sends service versions request -- SNAC(01,17)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object</param>
		public static void RequestServiceVersions(DataPacket dp)
		{
			Session sess = dp.ParentSession;
			// Construct SNAC (01,17)
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.GenericServiceControls;
			sh.FamilySubtypeID = (ushort)GenericServiceControls.ServerServicesVersionRequest;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			// Pack the family IDs and version numbers into the buffer
			int index = 0;
			ushort[] families = sess.Connections.GetFamilies(dp.ParentConnection);

			Array.Sort(families);
			Array.Reverse(families);
			
			byte[] buffer = new byte[families.Length * 4];
			foreach(ushort id in families)
			{
				ushort version = sess.Families.GetFamilyVersion(id);
				Marshal.InsertUshort(buffer, id, ref index);
				Marshal.InsertUshort(buffer, version, ref index);
			}

			dp.SNAC = sh;
			dp.Data = buffer;
			SNACFunctions.BuildFLAP(dp);
		}

		/// <summary>
		/// Processes the server's service version list -- SNAC(01,18)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(01,18)</param>
		public static void ProcessVersionsList(DataPacket dp)
		{
			byte[] SNACData = dp.Data;
			
			// This is pretty pointless, so we just move on
			SNAC01.RequestRateInformation(dp);
		}

		/// <summary>
		/// Sets the client's "Available" message -- SNAC(01,1E)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		/// <param name="availablemessage">The available message to set</param>
		public static void SetAvailableMessage(Session sess, string availablemessage)
		{
			SNAC01.SetExtendedStatus(sess, availablemessage, ICQFlags.Normal, ICQStatus.Online);
		}

		/// <summary>
		/// Sets the client's ICQ status -- SNAC(01,1E)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		/// <param name="flags">The ICQ flags to set</param>
		/// <param name="status">The ICQ status flags to set</param>
		public static void SetExtendedICQStatus(Session sess, ICQFlags flags, ICQStatus status)
		{
			SNAC01.SetExtendedStatus(sess, null, flags, status);
		}

		/// <summary>
		/// Sets the client's extended status -- SNAC(01,1E)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
		/// <param name="availablemessage">The available message to set</param>
		/// <param name="flags">The ICQ flags to set</param>
		/// <param name="status">The ICQ status to set</param>
    /// <remarks>Either the available message or the flags/status can be set in one call to SetExtendedStatus</remarks>
		private static void SetExtendedStatus(Session sess, string availablemessage, ICQFlags flags, ICQStatus status)
		{
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.GenericServiceControls;
			sh.FamilySubtypeID = (ushort)GenericServiceControls.SetExtendedStatus;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			int index = 0;
			// Figure out the buffersize
			int buffersize = 0;
      Encoding enc = Marshal.ASCII;
      ushort messagelength = (availablemessage != null) ? (ushort)enc.GetByteCount(availablemessage) : (ushort)0;

      if (availablemessage != null)
      {
        buffersize += 8 + messagelength + 4;
      }
      else
      {
        buffersize += 8;
      }

			byte[] buffer = new byte[buffersize];
			// Start putting information into the buffer
      if (availablemessage != null)
      {
        Marshal.InsertUshort(buffer, 0x001D, ref index);
        Marshal.InsertUshort(buffer, (ushort)(availablemessage.Length + 8), ref index);
        Marshal.InsertUshort(buffer, 0x0002, ref index);
        buffer[index++] = 0x04;
        buffer[index++] = (byte)(messagelength + 4);
        Marshal.InsertUshort(buffer, messagelength, ref index);
        Marshal.InsertString(buffer, availablemessage, enc, ref index);
        Marshal.InsertUshort(buffer, 0x0000, ref index);
      }
      else
      {
        Marshal.InsertUint(buffer, 0x00060004, ref index);
        uint stat = (uint)((ushort)flags << 16);
        stat |= (ushort)status;
        Marshal.InsertUint(buffer, stat, ref index);
      }

			DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
			dp.ParentConnection = sess.Connections.BOSConnection;
			SNACFunctions.BuildFLAP(dp);
		}

		/// <summary>
		/// Processes a request for client verification -- SNAC(01,1F)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(01,1F)</param>
    /// <remarks>See the entry for <see cref="SendVerificationResponse"/> for a description of
    /// how this SNAC is used.</remarks>
		public static void ProcessVerificationRequest(DataPacket dp)
		{
			byte[] SNACData = dp.Data;

			int index = 0;
			uint offset = Marshal.ByteArrayToUint(SNACData, ref index);
			uint length = Marshal.ByteArrayToUint(SNACData, ref index);

			SNAC01.SendVerificationResponse(dp.ParentSession);
		}

		/// <summary>
		/// Sends a response to a client verification request -- SNAC(01,20)
		/// </summary>
		/// <param name="sess">A <see cref="Session"/> object</param>
    /// <remarks>
    /// SNAC(01,1F) and SNAC(01,20) are used to verify that the client connecting
    /// to the network was the official AOL Instant Messenger.  The server sends
    /// two integers, an offset and a length, in SNAC(01,1F).  These parameters are then used to
    /// get 16 bytes of data in the offical client's static memory region. However, after
    /// AOL begain using this authentication method, the servers never changed the requested
    /// offset and length. The expected response was the same 16 bytes every time, which made
    /// it a fairly ineffective deterrent to "unauthorized" clients. This SNAC pair not been
    /// reported as being used in years.
    /// </remarks>
		public static void SendVerificationResponse(Session sess)
		{
			SNACHeader sh = new SNACHeader();
			sh.FamilyServiceID = (ushort)SNACFamily.GenericServiceControls;
			sh.FamilySubtypeID = (ushort)GenericServiceControls.ClientVertificationResponse;
			sh.Flags = 0x0000;
			sh.RequestID = sess.GetNextRequestID();

			int index = 0;
			byte[] buffer = new byte[16];
			Marshal.InsertUint(buffer, 0x44a95d26, ref index);
			Marshal.InsertUint(buffer, 0xd2490423, ref index);
			Marshal.InsertUint(buffer, 0x93b8821f, ref index);
			Marshal.InsertUint(buffer, 0x51c54b01, ref index);

			DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
			dp.ParentConnection = sess.Connections.BOSConnection;
			SNACFunctions.BuildFLAP(dp);
		}

    /// <summary>
    /// Processes the client's own extended status update sent by the server -- SNAC(01,21)
    /// </summary>
    /// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(01,21)</param>
		public static void ProcessExtendedStatus(DataPacket dp)
		{
      // Messages are starting with a 0x0004...what the heck?
			int index = 2;

			ushort type = Marshal.ByteArrayToUshort(dp.Data, ref index);
			byte flags = dp.Data[index++];
			byte length = dp.Data[index++];

			if(type == SNAC01.EXTSTATUS_ICON1 || type == SNAC01.EXTSTATUS_ICON2)
			{
				byte[] hash = new byte[length];
				Marshal.CopyArray(dp.Data, hash, ref index);

				if(flags == SNAC01.ICONFLAGS_REQUESTICON)
				{
					if(dp.ParentSession.UploadIconFilename != "")
					{
						dp.ParentSession.SSI.UpdateIconHash();
					}
					else
					{
						dp.ParentSession.SSI.RemoveIconHash();
					}
				}
				else if(flags == SNAC01.ICONFLAGS_UPLOADICON)
				{
					SNAC10.UploadIcon(dp.ParentSession);
				}
				else if(flags == SNAC01.ICONFLAGS_UPLOADSUCCESS)
				{
					// Icon upload succeeded. Signaled to the user in SNAC10
				}
			}
			else // Available message
			{
			}
		}
	}
}
